04 函数表达式
函数声明与函数表达式的差异
foo();
function foo() {
console.log("foo");
}
// foo 函数被正确执行
foo()
var foo = function (){
console.log('foo')
}
// 变量 foo 并不是一个函数,无法被调用
VM130:1 Uncaught TypeError: foo is not a function
at <anonymous>:1:1
这两种定义函数的方式具有不同语义,不同的语义触发了不同的行为。第一种称之为函数声明,第二种称之为函数表达式。
V8 是怎么处理函数声明的
函数声明定义了一个具有指定参数的函数:
function name([param,[, param,[..., param]]]) {
[statements]
}
V8 在执行 JavaScript 的过程中,会先对其进行编译,然后再执行:
var x = 5;
function foo() {
console.log("Foo");
}
在编译阶段,如果解析到函数声明,那么 V8 会将这个函数声明转换为内存中的函数对象,并将其放到作用域中。同样,如果解析到了某个变量声明,也会将其放到作用域中,但是会将其值设置为 undefined,表示该变量还未被使用。
在 V8 执行阶段,如果使用了某个变量,或者调用了某个函数,V8 便会去作用域查找相关内容。
可以使用 D8 来查看作用域的数据,将这段代码保存到 test.js 中,使用 d8 --print-scopes test.js
命令即可查看作用域的状态:
Global scope:
global { // (0x7fb62281ca48) (0, 50)
// will be compiled
// 1 stack slots
// temporary vars:
TEMPORARY .result; // (0x7fb62281cfe8) local[0]
// local vars:
VAR x; // (0x7fb62281cc98)
VAR foo; // (0x7fb62281cf40)
function foo () { // (0x7fb62281cd50) (22, 50)
// lazily parsed
// 2 heap slots
}
}
作用域中包含了变量 x 和 foo,变量 x 的默认值是 undefined,变量 foo 指向了 foo 函数对象,foo 函数对象被 V8 存放在内存中的堆空间了,这些变量都是在编译阶段被装进作用域中的。
在执行之前,这些变量都被提升到作用域中了,所以在执行阶段,V8 当然就能获取到所有的定义变量了。把这种在编译阶段,将所有的变量提升到作用域的过程称为变量提升。
- 普通变量,变量提升之后的值都是 undefined,
- 声明的函数,变量提升之后的值都是函数对象
表达式就是表示值的式子,执行这段代码会返回一个值:
x = 5;
6 === 5;
语句是操作值的式子,是一个语句,执行该语句时 V8 并不会返回一个值。
var x;
function foo() {
return 1;
}
// 执行到这段代码时,V8 并没有返回任何的值,它只是解析 foo 函数,并将函数对象存储到内存中。
在 V8 执行var x = 5
这段代码时,会认为它是两段代码,一段是定义变量的语句,一段是赋值的表达式,
var x = undefined; // 语句,在编译阶段完成,即提升变量阶段
x = 5; // 表达式,在执行阶段完成
函数声明不输出任何内容,因此是一个语句,V8 会对函数声明执行变量提升。函数也是一个对象,所以在编译阶段,V8 就会将整个函数对象提升到作用域中,并不是给该函数名称赋一个 undefined。
V8 是怎么处理函数表达式的
在一个表达式中使用 function 来定义一个函数,那么就把该函数称为函数表达式。
foo = function () {
console.log("foo");
};
分析函数表达式:
foo();
var foo = function () {
console.log("foo");
};
var foo = undefined;
foo = function () {
console.log("foo");
};
立即调用的函数表达式(IIFE)
(function () {
//statements
});
小括号之间存放的必须是表达式,所以如果在小阔号里面定义一个函数,那么 V8 就会把这个函数看成是函数表达式,执行时它会返回一个函数对象。
直接在表达式后面加上调用的括号,这就称为立即调用函数表达式(IIFE):
(function () {
//statements
})();
函数立即表达式也是一个表达式,V8 在编译阶段,并不会为该表达式创建函数对象。好处是不会污染环境,函数和函数内部的变量都不会被其他部分的代码访问到。